//
//  Pump.m
//  PezMonkey
//
//  Created by Spence DiNicolantonio on 12/23/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

#import "Pump.h"

#define kPumpMotorCoefFast 10
#define kPumpMotorCoefNormal 5
#define kPumpMotorCoefSlow 1
#define kPumpMotorRate 20
#define kPumpBallCount 5

@implementation Pump

@synthesize motor;
@synthesize balls;
@synthesize staticBody;

- (NSString *)demoName {
	return @"Pump";
}

- (void)start {
	// create space
	self.space = [PMSpace spaceWithGravity:PMVectMake(0, -1)];
	[space setSubSteps:3];
	[space setIterations:10];
	
	// create shared static body
	self.staticBody = [PMBody staticBody];
	
	
	// create hopper segments
	[self addSegmentWithStart:PMVectMake(-256,16) end:PMVectMake(-256,240)];
	[self addSegmentWithStart:PMVectMake(-256,16) end:PMVectMake(-192,0)];
	[self addSegmentWithStart:PMVectMake(-192,0) end:PMVectMake(-192, -64)];
	[self addSegmentWithStart:PMVectMake(-128,-64) end:PMVectMake(-128,144)];
	[self addSegmentWithStart:PMVectMake(-192,80) end:PMVectMake(-192,176)];
	[self addSegmentWithStart:PMVectMake(-192,176) end:PMVectMake(-128,240)];
	[self addSegmentWithStart:PMVectMake(-128,144) end:PMVectMake(192,64)];
	
	
	PMShape *shape;
	PMConstraint *joint;
	
	
	 // define plunger vertices
	PMFloat plungerVertCount = 4;
	PMVect plungerVerts[] = {
		PMVectMake(-30,-80),
		PMVectMake(-30, 80),
		PMVectMake( 30, 64),
		PMVectMake( 30,-80),
	};
	
	// create plunger
	PMBody *plunger = [PMBody bodyWithMass:1.0 
									moment:PMInfinity];
	[plunger setPosition:PMVectMake(-160, -80)];
	[space addBody:plunger];
	
	// create plunger shape
	shape = [PMPolygonShape polygonShapeWithBody:plunger 
										vertices:plungerVerts 
									 vertCount:plungerVertCount];
	[shape setFriction:0.5];
	[shape setElasticity:1.0];
	[shape setLayers:1];
	[space addShape:shape];
	
	
	// initialize balls array
	self.balls = [NSMutableArray array];
	
	// add balls to hopper
	for (int i=0; i<kPumpBallCount; i++) {
		PMBody *ball = [self addBallAtPoint:PMVectMake(-224, 80 + 64*i)];
		[balls addObject:ball];
	}
	
	
	// create small gear body
	PMFloat smallGearMass = 10;
	PMFloat smallGearRadius = 80;
	PMFloat smallGearMoment = PMMomentForCircle(smallGearMass, 0, smallGearRadius, PMVectZero);
	PMBody *smallGear = [PMBody bodyWithMass:smallGearMass 
									  moment:smallGearMoment];
	[smallGear setPosition:PMVectMake(-160, -160)];
	[smallGear setAngle:-M_PI_2];
	[space addBody:smallGear];
	
	// create small gear shape
	shape = [PMCircleShape circleShapeWithBody:smallGear 
										radius:smallGearRadius];
	[shape setLayers:4];
	[space addShape:shape];
	
	// "pin" the small gear to the screen
	joint = [PMPivotJoint pivotJointWithBodyA:staticBody 
										bodyB:smallGear 
										pivot:smallGear.position];
	[space addConstraint:joint];
		
	
	// create big gear body
	PMFloat bigGearMass = 40;
	PMFloat bigGearRadius = (2 * smallGearRadius);
	PMFloat bigGearMoment = PMMomentForCircle(bigGearMass, 0, bigGearRadius, PMVectZero);
	PMBody *bigGear = [PMBody bodyWithMass:bigGearMass 
									moment:bigGearMoment];
	[bigGear setPosition:PMVectMake(80, -160)];
	[bigGear setAngle:M_PI_2];
	[space addBody:bigGear];
	
	// create big gear shape
	shape = [PMCircleShape circleShapeWithBody:bigGear 
										radius:bigGearRadius];
	[shape setLayers:4];
	[space addShape:shape];
	
	// "pin" the big gear to the screen
	joint = [PMPivotJoint pivotJointWithBodyA:staticBody 
										bodyB:bigGear 
										pivot:bigGear.position];
	[space addConstraint:joint];
	

	// connect plunger to small gear.
	joint = [PMPinJoint pinJointWithAnchor:PMVectMake(smallGearRadius, 0) 
								   onBodyA:smallGear 
									anchor:PMVectZero 
								   onBodyB:plunger];
	[space addConstraint:joint];
	
	// connect gears.
	joint = [PMGearJoint gearJointWithBodyA:smallGear 
									  bodyB:bigGear
									  phase:-M_PI_2
									  ratio:-(smallGearRadius / bigGearRadius)];
	[space addConstraint:joint];
	
	
	// define feeder mechanism attributes
	PMFloat feederBottom = -300.0;
	PMFloat feederTop = 32.0;
	PMFloat feederMass = 1.0;
	PMFloat feederWidth = 40;
	PMFloat feederMoment = PMMomentForSegment(feederMass, PMVectMake(0, feederBottom), PMVectMake(0, feederTop));
	
	// create feeder body
	PMBody *feeder = [PMBody bodyWithMass:feederMass 
								   moment:feederMoment];
	[feeder setPosition:PMVectMake(-224, (feederBottom + feederTop) / 2)];
	[space addBody:feeder];
	
	// create feeder shape
	PMFloat feederLength = (feederTop - feederBottom);
	shape = [PMSegmentShape segmentShapeWithBody:feeder 
										   start:PMVectMake(0, feederLength / 2) 
											 end:PMVectMake(0, -feederLength / 2)
										   width:feederWidth];
	[shape setLayers:2];
	[space addShape:shape];
	
	// "pin" the feeder to the screen
	joint = [PMPivotJoint pivotJointWithAnchor:PMVectMake(-224, feederBottom) 
									   onBodyA:staticBody 
										anchor:PMVectMake(0, -feederLength / 2) 
									   onBodyB:feeder];
	[space addConstraint:joint];
	
	
	// connect feeder to small gear
	PMVect anchor = [feeder convertPointToLocal:PMVectMake(-224, -160)];
	joint = [PMPinJoint pinJointWithAnchor:anchor 
								   onBodyA:feeder 
									anchor:PMVectMake(0, smallGearRadius) 
								   onBodyB:smallGear];
	[space addConstraint:joint];
	
	// motorize the big gear
	self.motor = [PMSimpleMotor simpleMotorWithBodyA:staticBody 
											   bodyB:bigGear 
												rate:0];
	[space addConstraint:motor];
}

- (void)update:(NSTimeInterval)deltaT {
	// determine motor rate
	PMFloat rate = 0;
	if (leftArrow^rightArrow)
		rate = (leftArrow)? -kPumpMotorRate : kPumpMotorRate;
	
	// set motor rate
	[motor setRate:rate];
	
	// determine motor max speed coefficient
	PMFloat coef = kPumpMotorCoefNormal;
	if (upArrow^downArrow)
		coef = (upArrow)? kPumpMotorCoefFast : kPumpMotorCoefSlow;
		
	// update max force
	PMFloat max = (rate)? 100000.0 : 0.0;
	max *= coef;
	[motor setMaxForce:max];
	
	// reset balls
	for (PMBody *ball in balls) {
		if (ball.position.x > 320.0) {
			[ball setVelocity:PMVectZero];
			[ball setPosition:PMVectMake(-224.0, 320.0)];
		}
	}
}

/**
 * Creates a ball at a given position and adds it to the space. The balls corresponding
 * PMBody is returned.
 */
- (PMBody *)addBallAtPoint:(PMVect)point {
	// define ball attributes
	PMFloat radius = 29.9;
	PMFloat mass = 1.0;
	PMFloat moment = PMMomentForCircle(mass, 0, radius, PMVectZero);
	
	// create ball body
	PMBody *body = [PMBody bodyWithMass:mass moment:moment];
	[body setPosition:point];
	[space addBody:body];
	
	// create ball shape
	PMShape *shape = [PMCircleShape circleShapeWithBody:body radius:radius];
	[shape setFriction:0.5];
	[shape setElasticity:0.0];
	[space addShape:shape];
	
	// return ball body
	return body;
}
			 
/**
 * Creates a segment shape at a given start and end points and adds it to the
 * space.
 */
- (void)addSegmentWithStart:(PMVect)start end:(PMVect)end {
	// create segment shape
	PMFloat width = 4;
	PMShape *shape = [PMSegmentShape segmentShapeWithBody:staticBody 
													start:start 
													  end:end
													width:width];
	[shape setFriction:0.5];
	[shape setElasticity:0.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[space addShape:shape];
}

	 
	 
#pragma mark -
#pragma mark Dealloc

- (void)dealloc {
	[motor release];
	[balls release];
	[staticBody release];
	[super dealloc];
}		 

@end
